/****************************************************************
 *								*
 *	Copyright 2001, 2008 Fidelity Information Services, Inc	*
 *								*
 *	This source code contains the intellectual property	*
 *	of its copyright holder(s), and is made available	*
 *	under a license.  If you do not know the terms of	*
 *	the license, please stop and do not read further.	*
 *								*
 ****************************************************************/

#include "mdef.h"

#include <sys/types.h>
#include <signal.h>
#include "gtm_unistd.h"
#include "gtm_string.h"
#include "gtm_stdio.h"

#include "error.h"
#include "io_params.h"
#include "op.h"
#include "io.h"
#include "rtnhdr.h"
#include "stack_frame.h"
#include "jobexam_process.h"
#ifdef UNIX
#  include "jobexam_signal_handler.h"
#endif
#include "send_msg.h"
#include "callg.h"
#include "zshow.h"
#include "util.h"
#include "hashtab_mname.h"	/* needed for lv_val.h */
#include "lv_val.h"
#include "mv_stent.h"

#define DEFAULT_DUMP_FILENAME "GTM_JOBEXAM.ZSHOW_DMP"
#define NOCONCEAL_OPTION "NO_CONCEAL"

static readonly mval empty_str_mval = DEFINE_MVAL_LITERAL(MV_STR, 0, 0, 0, 0, 0, 0);
static readonly mval no_conceal_op  = DEFINE_MVAL_LITERAL(MV_STR, 0, 0, sizeof(NOCONCEAL_OPTION) - 1,
							 NOCONCEAL_OPTION, 0, 0);
static unsigned char dumpable_error_dump_file_parms[2] = {iop_newversion, iop_eol};
static unsigned char dumpable_error_dump_file_noparms[1] = {iop_eol};
static unsigned int  jobexam_counter;

GBLREF uint4		process_id;
GBLREF io_pair		io_std_device, io_curr_device;
GBLREF mv_stent		*mv_chain;
GBLREF unsigned char    *msp, *stackwarn, *stacktop;
GBLREF char		*util_outptr, util_outbuff[OUT_BUFF_SIZE];
GBLREF boolean_t        created_core;
UNIX_ONLY(GBLREF sigset_t blockalrm;)

error_def(ERR_GTMASSERT);
error_def(ERR_GTMCHECK);
error_def(ERR_MEMORY);
error_def(ERR_VMSMEMORY);
error_def(ERR_OUTOFSPACE);
error_def(ERR_STACKOFLOW);
error_def(ERR_STACKCRIT);
error_def(ERR_JOBEXAMFAIL);
error_def(ERR_JOBEXAMDONE);

void jobexam_process(mval *dump_file_name, mval *dump_file_spec)
{
	mval			*input_dump_file_name;
	io_pair			dev_in_use;
	mv_stent		*new_mv_stent;
	boolean_t		saved_mv_stent;
	char			saved_util_outbuff[OUT_BUFF_SIZE];
	int			saved_util_outbuff_len;
#ifdef UNIX
	struct sigaction	new_action, prev_action;
	sigset_t		savemask;
#endif
	/* If the input file name is the result of an expression, it is likely being held in the
	   same temporary as the output file spec. We can tell if this is true by comparing the
	   address of the input and output mvals. If they are the same, make a copy of the input
	   filespec in a garbage collection safe mval prior to initializing the output mval
	   (which in this case would clear the input mval as well if it had not just been saved).
	*/
	if (dump_file_name == dump_file_spec)
	{	/* Make saved copy of input mval */
		PUSH_MV_STENT(MVST_MVAL);
		new_mv_stent = mv_chain;
		input_dump_file_name = &mv_chain->mv_st_cont.mvs_mval;
		*input_dump_file_name = *dump_file_name;
		saved_mv_stent = TRUE;
	} else
	{	/* Just use input mval as-is */
		input_dump_file_name = dump_file_name;
		saved_mv_stent = FALSE;
	}

#ifdef UNIX
	/* Block out timer calls that might trigger processing that could fail. We especially want to prevent
	   nesting of signal handlers since the longjump() function used by the UNWIND macro is undefined on
	   Tru64 when signal handlers are nested.
	*/
	sigprocmask(SIG_BLOCK, &blockalrm, &savemask);

	/* Setup new signal handler to just drive condition handler which will do the right thing */
	memset(&new_action, 0, sizeof(new_action));
	sigemptyset(&new_action.sa_mask);
	new_action.sa_flags = SA_SIGINFO;
#ifdef __sparc
	new_action.sa_handler = jobexam_signal_handler;
#else
	new_action.sa_sigaction = jobexam_signal_handler;
#endif
	sigaction(SIGBUS, &new_action, &prev_action);
	sigaction(SIGSEGV, &new_action, 0);
#endif
	*dump_file_spec = empty_str_mval;
	dev_in_use = io_curr_device;		/* Save current IO device */
	/* Save text in util_outbuff which can be detrimentally overwritten by ZSHOW */
	saved_util_outbuff_len = 0;
	if (NULL == util_outptr)
		util_outptr = util_outbuff;
	if (0 != (saved_util_outbuff_len = (util_outptr - util_outbuff)))	/* Caution -- assignment */
	{
		assert(0 <= saved_util_outbuff_len);
		assert(saved_util_outbuff_len <= sizeof(saved_util_outbuff));
		memcpy(saved_util_outbuff, util_outbuff, saved_util_outbuff_len);
	}
	jobexam_dump(input_dump_file_name, dump_file_spec);
	/* If any errors occur in job_exam_dump, the condition handler will unwind the stack to this point and return.  */
	if (0 != saved_util_outbuff_len)
	{	/* Restore util_outbuff values */
		memcpy(util_outbuff, saved_util_outbuff, saved_util_outbuff_len);
		util_outptr = util_outbuff + saved_util_outbuff_len;
	}
	io_curr_device = dev_in_use;		/* Restore IO device */
	/* If we saved an mval on our stack, we need to pop it off. If there was an error while doing the
	 * jobexam dump, zshow may have left some other mv_stent entries on the stack. Pop them all off with
	 * just a regular POP_MV_STENT macro rather than unw_mv_ent() call because the mv_stent entries
	 * created in zshow_output reference automatic storage that cannot be referenced at this stack
	 * level without potential (C) stack corruption.
	 */
	if (saved_mv_stent)
	{
		if (mv_chain > new_mv_stent)
			/* This violates our assumptions that the mv_stent we pushed onto the stack should
			   still be there */
			GTMASSERT;
		while (mv_chain <= new_mv_stent)
		{
			POP_MV_STENT();
		}
	}
#ifdef UNIX
	/* Restore the signal handlers how they were */
	sigaction(SIGBUS, &prev_action, 0);
	sigaction(SIGSEGV, &prev_action, 0);
	/* Let the timers pop again.. */
	sigprocmask(SIG_SETMASK, &savemask, NULL);
#endif
}

/* This routine is broken out as another ep so we can do cleanup processing in jobexam_process if
   we trigger the condition handler and unwind.
*/
void jobexam_dump(mval *dump_filename_arg, mval *dump_file_spec)
{
	unsigned char		dump_file_name[50], *dump_file_name_ptr;
	mval			def_file_name, parms, zshowall;

	ESTABLISH(jobexam_dump_ch);

	++jobexam_counter;
	/* Setup default filename/type to use for the parse. Append processid and a counter. */
	MEMCPY_LIT(dump_file_name, DEFAULT_DUMP_FILENAME);
	dump_file_name_ptr = dump_file_name + sizeof(DEFAULT_DUMP_FILENAME) - 1;
	*dump_file_name_ptr++ = '_';
	dump_file_name_ptr = i2asc(dump_file_name_ptr, process_id);
	*dump_file_name_ptr++ = '_';
	dump_file_name_ptr = i2asc(dump_file_name_ptr, jobexam_counter);
	def_file_name.mvtype = MV_STR;
	def_file_name.str.addr = (char *)dump_file_name;
	def_file_name.str.len = INTCAST(dump_file_name_ptr - dump_file_name);
	/* Call $ZPARSE processing to fill in any blanks, expand concealed logicals, etc. It is the callers
	   responsibility to make sure garbage collection knows about the value in the returned filespec.
	*/
	op_fnzparse(dump_filename_arg, &empty_str_mval, &def_file_name, &empty_str_mval, &no_conceal_op, dump_file_spec);
	/* Parms of file to be created (newversion) */
	parms.mvtype = MV_STR;
	parms.str.addr = (char *)dumpable_error_dump_file_parms;
	parms.str.len = sizeof(dumpable_error_dump_file_parms);
	/* Open, use, and zshow into new file, then close and reset current io device */
	op_open(dump_file_spec, &parms, 0, 0);
	op_use(dump_file_spec, &parms);
	zshowall.mvtype = MV_STR;
	zshowall.str.addr = "*";
	zshowall.str.len = 1;
	op_zshow(&zshowall, ZSHOW_DEVICE, NULL);
	parms.str.addr = (char *)dumpable_error_dump_file_noparms;
	parms.str.len = sizeof(dumpable_error_dump_file_noparms);
	op_close(dump_file_spec, &parms);
	/* Notify operator dump was taken */
	send_msg(VARLSTCNT(5) ERR_JOBEXAMDONE, 3, process_id, dump_file_spec->str.len, dump_file_spec->str.addr);
	REVERT;
}

CONDITION_HANDLER(jobexam_dump_ch)
{
	boolean_t	save_created_core;

	START_CH;

	/* Operation:
	   1) Flush out message we came here because of to operator console
	   2) Put out our message stating that we screwed up
	   3) Unwind the errant frames so we can return to the user without screwing
	      up the task that got interrupted to do this examine.
	*/
#if defined(DEBUG) && defined(UNIX)
	if (DUMPABLE)
	{	/* For debug UNIX issues, let's make a core if we would have made one in open code */
		save_created_core = created_core;
		gtm_fork_n_core();
		created_core = save_created_core;
	}
#endif
	UNIX_ONLY(util_out_print(0, OPER));
	VMS_ONLY(sig->chf$l_sig_args -= 2);
	VMS_ONLY(callg(send_msg, &sig->chf$l_sig_args));
	send_msg(VARLSTCNT(3) ERR_JOBEXAMFAIL, 1, process_id);

	/* Stop the errors here and return to caller */
	UNWIND(NULL, NULL);
}
